Skip to content

playback: smooth manual track switches and play/pause - #1740

Open
stappmus wants to merge 2 commits into
librespot-org:devfrom
stappmus:fix/manual-track-switch-pop
Open

playback: smooth manual track switches and play/pause#1740
stappmus wants to merge 2 commits into
librespot-org:devfrom
stappmus:fix/manual-track-switch-pop

Conversation

@stappmus

@stappmus stappmus commented Aug 15, 2026

Copy link
Copy Markdown

Summary

Smooth abrupt PCM steps so they do not reach the audio backend:

  • append a 20 ms linear ramp from the last submitted stereo frame to silence before loading a different track, pausing, or stopping
  • ramp the first 20 ms of decoded audio from silence to full level after a manual track switch or resume
  • leave natural end-of-track gapless transitions unchanged

This is a follow-up to #1213 and #1223. Keeping the sink open avoids a device reopen on track change, but a manually selected, un-preloaded track can still begin at a very different sample value. Pause still stops the sink, and resume starts it again; both used to do that on a non-zero sample. Those discontinuities are audible as a click or pop on some outputs. The short ramp implements the mute/unmute approach suggested in the issue discussion.

The track-switch ramp is limited to loads of a different track while the player is already Playing or Paused, and only when gapless mode is enabled. Pause and resume use the same envelope regardless of gapless, because they still close and reopen the sink. This does not change the public API, normal seeks, natural transitions, or passthrough packets.

Testing

  • cargo fmt --all -- --check
  • cargo build
  • cargo clippy
  • cargo test --workspace
  • cargo test -p librespot-playback --no-default-features --features pulseaudio-backend,native-tls

Unit tests cover exact fade endpoints, stereo channel alignment, fade-in state spanning decoder packet boundaries, and a no-op fade-in once the envelope is finished.

I also backported the same player change to librespot 0.8.0 in spotifyd 0.4.2 and tested repeated manual selections through the PulseAudio backend on PipeWire. The previously reproducible pop was no longer audible.

Copilot AI lite review requested due to automatic review settings August 15, 2026 22:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves user-initiated track changes during gapless playback by adding a short fade-out/fade-in envelope around manual track switches, reducing audible clicks/pops caused by PCM discontinuities while keeping natural end-of-track gapless transitions unchanged.

Changes:

  • Add a 20ms fade-out to silence based on the last submitted stereo frame when manually switching tracks in gapless mode.
  • Apply a 20ms fade-in from silence on the decoded PCM of the newly selected track, including across packet boundaries.
  • Add unit tests for fade endpoints and stereo/frame alignment, and document the fix in the changelog.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
playback/src/player.rs Implements manual-switch fade-out/fade-in logic gated on gapless + track change, tracks last output frame, and adds unit tests.
CHANGELOG.md Adds a “Fixed” entry documenting the manual gapless track-switch click prevention.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@edbr-xyz

Copy link
Copy Markdown

Would this apply to all play/pauses in the middle of songs?

I get quite a decent pop from the speakers whenever I hit pause in the middle of a song.

Reuse the 20 ms PCM envelope for mid-track pause and resume so the
sink is not closed or reopened on a non-zero sample.
@stappmus stappmus changed the title playback: smooth manual track switches playback: smooth manual track switches and play/pause Aug 19, 2026
@stappmus

stappmus commented Aug 19, 2026

Copy link
Copy Markdown
Author

Thanks — it didn't originally, but it does now.

Pause and resume use the same 20 ms fade as manual track switches: last frame down to silence before the sink stops, then the first decoded audio back in on play.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants